﻿// $Id$
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Printing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Globalization;
using System.Deployment.Application;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Configuration;
using ZedGraph;

namespace QuantumFractals
{
    public partial class MainForm : Form
    {
        private delegate void showInfoCallback();
        private int startMiliseconds = 0;
        private bool running = false;
        private QFractal quantumFractal = null;
        private QRenderer renderer = null;
        public QRenderer Renderer { get { return renderer; } }
        public QFractal QuantumFractal { get { return quantumFractal; } }

        public MainForm(string[] args)
        {
            InitializeComponent();
            Text = GetTitle();

            if (args.Length > 0)
                SetDefaults(args[0]);
            else
                SetDefaults(Properties.Resources.CubeXmlFractal);
        }

        public void SetDefaults(string xmlFractal)
        {
            quantumFractal = new QFractal(xmlFractal);
            renderer = new QRenderer(Properties.Settings.Default.ResolutionPixels,
                Properties.Settings.Default.ResolutionBalls);
            fractalPanel.Bitmap = new Bitmap(renderer.PixelResolution, renderer.PixelResolution);
            stMessage.Text = "Ready";
            stResolution.Text = String.Format("{0}x{0}", renderer.PixelResolution);
            stName.Text = quantumFractal.Name;
            ShowStatusTime();
        }

        private string GetTitle()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            object[] attributes;
            attributes = assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false);
            string product = ((AssemblyProductAttribute)attributes[0]).Product;
            string version = assembly.GetName().Version.ToString();
            attributes = assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
            string company = ((AssemblyCompanyAttribute)attributes[0]).Company;
            attributes = assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
            string copyright = ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
            return String.Format("{0} version {1} {2} {3}", 
                product, version, copyright, company);
        }

        private void btnHomePage_Click(object sender, EventArgs e)
        {
            Process.Start("http://quantumfractals.codeplex.com");
        }

        private void btnCalculate_Click(object sender, EventArgs e)
        {
            btnCalculate.Enabled = false;
            btnStop.Enabled = true;
            stProgress.Minimum = renderer.SumPixelDensity.All;
            stProgress.Maximum = renderer.SumPixelDensity.All + Properties.Settings.Default.TimeCounter;
            stMessage.Text = "Calculating fractal 3d points";
            startMiliseconds = Environment.TickCount;
            running = true;
            worker.RunWorkerAsync(stProgress);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            worker.CancelAsync();
        }

        private void ShowStatusTime()
        {
            if (statusStrip.InvokeRequired)
            {
                showInfoCallback d = new showInfoCallback(ShowStatusTime);
                Invoke(d);
            }
            else
            {
                stProgress.Value = Math.Min(renderer.SumPixelDensity.All, stProgress.Maximum);
                string timeText = stProgress.Value.ToString("### ### ### ### ###");
                /*if (running)
                {
                    int msec = Math.Abs(Environment.TickCount - startMiliseconds);
                    int sumOfMsec = (stProgress.Maximum * msec) / (time + 1);
                    string remainText = "";
                    if (sumOfMsec < 60 * 1000)
                        remainText = String.Format("{0} second(s)", sumOfMsec / 1000);
                    else if (sumOfMsec < 60 * 60 * 1000)
                        remainText = String.Format("{0} minute(s)", sumOfMsec / 1000 / 60);
                    else 
                        remainText = String.Format("{0} hour(s)", sumOfMsec / 1000 / 60 / 60);
                    stTime.Text = String.Format("Points: {0} Remain: {1}. ", timeText, remainText);
                }
                else*/
                {
                    stTime.Text = String.Format("Points:{0}. ", timeText);
                }
                Application.DoEvents();
            }
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int t = 0; t < Properties.Settings.Default.TimeCounter; t++)
            {
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
                renderer.AddPoint(quantumFractal.CalculateNextPoint());
                if (t % 100000 == 0)
                    ShowStatusTime();
            }
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            running = false;
            btnDraw_Click(sender, e);
            btnCalculate.Enabled = true;
            btnStop.Enabled = false;
            if (e.Error != null)
                MessageBox.Show(e.Error.Message);
            else if (e.Cancelled)
                stMessage.Text = "Canceled";
            else
            {
                stMessage.Text = String.Format("Calculated {0} fractal 3d points",
                    Properties.Settings.Default.TimeCounter);
            }
            stProgress.Value = stProgress.Minimum;
            ShowStatusTime();
        }

        private void btnAbout_Click(object sender, EventArgs e)
        {
            using (AboutForm form = new AboutForm())
            {
                form.ShowDialog(this);
            }
        }

        private void btnSettings_Click(object sender, EventArgs e)
        {
            using (SettingsForm form = new SettingsForm())
            {
                if (form.ShowDialog(this) == DialogResult.OK)
                    if (MessageBox.Show("Reset fractal data?", "Confirmation", MessageBoxButtons.OKCancel) ==
                        DialogResult.OK)
                        btnClear_Click(sender, e);
            }
        }

        private void btnHelp_Click(object sender, EventArgs e)
        {
            Process.Start("QuantumFractals.pdf");
        }

        private void btnProperties_Click(object sender, EventArgs e)
        {
            using (PropertiesForm form = new PropertiesForm())
            {
                NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
                form.FractalName = quantumFractal.Name;
                form.Description = quantumFractal.Description;
                form.Author = quantumFractal.Author;
                form.CreationDate = quantumFractal.CreationDate;
                form.FormulaAlpha = quantumFractal.FormulaAlpha;
                form.FormulaDetectors = quantumFractal.FormulaDetectors;
                form.FormulaConstants = quantumFractal.FormulaConstants;

                if (form.ShowDialog(this) == DialogResult.OK)
                {

                    quantumFractal = new QFractal(form.FormulaAlpha, form.FormulaDetectors, form.FormulaConstants,
                        form.FractalName, form.Description, form.Author, form.CreationDate);
                    renderer = new QRenderer(Properties.Settings.Default.ResolutionPixels,
                        Properties.Settings.Default.ResolutionBalls);
                    fractalPanel.Bitmap = new Bitmap(renderer.PixelResolution, renderer.PixelResolution);
                    stResolution.Text = String.Format("{0}x{0}", renderer.PixelResolution);
                    stMessage.Text = "Fractal properties changed";
                    stName.Text = quantumFractal.Name;
                }
            }
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            saveFileDialog.FileName = quantumFractal.Name;
            if (Properties.Settings.Default.FractalsDirectory == "")
                saveFileDialog.InitialDirectory = 
                    Environment.GetFolderPath(Environment.SpecialFolder.Personal) +
                    @"\Fractals";
            else
                saveFileDialog.InitialDirectory = Properties.Settings.Default.FractalsDirectory;
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                Cursor = Cursors.WaitCursor;
                try
                {
                    switch (saveFileDialog.FilterIndex)
                    {
                        case 1:
                            quantumFractal.SaveToXml(saveFileDialog.FileName);
                            stMessage.Text = "Fractal properties saved to file";
                            break;
                        case 2:
                            fractalPanel.Bitmap.Save(saveFileDialog.FileName, ImageFormat.Bmp);
                            stMessage.Text = "Fractal image saved to PNG file";
                            break;
                        case 3:
                            stMessage.Text = "Exporting fractal 3d points to Csv file";
                            Application.DoEvents();
                            quantumFractal.SaveToCsv(saveFileDialog.FileName,
                                GetSeparatorChar(Properties.Settings.Default.CsvSeparator),
                                GetCultureName(), Properties.Settings.Default.TimeCounter);
                            stMessage.Text = "Fractal 3d points exported to Csv file";
                            break;
                        case 4:
                            stMessage.Text = "Exporting fractal 2d points to Csv file";
                            Application.DoEvents();
                            renderer.SaveToCsv(saveFileDialog.FileName,
                                GetSeparatorChar(Properties.Settings.Default.CsvSeparator));
                            stMessage.Text = "Fractal 2d points exported to Csv file";
                            break;
                        case 5:
                            stMessage.Text = "Exporting fractal to Powray file";
                            Application.DoEvents();
                            renderer.SaveAsPovray(saveFileDialog.FileName);
                            stMessage.Text = "Fractal exported to Powray file";
                            break;
                    }
                }
                finally
                {
                    Cursor = Cursors.Default;
                }
                Properties.Settings.Default.FractalsDirectory = Path.GetDirectoryName(
                    saveFileDialog.FileName);
                Properties.Settings.Default.Save();
                Application.DoEvents();
                if (saveFileDialog.FilterIndex > 1 &&
                    MessageBox.Show(String.Format("Open {0} file in associated program?", 
                        Path.GetFileName(saveFileDialog.FileName)), "Question",
                    MessageBoxButtons.YesNo) == DialogResult.Yes)
                        Process.Start(saveFileDialog.FileName);
            }
        }

        public char GetSeparatorChar(string text)
        {
            switch (text.ToLower())
            {
                case "{tab}":
                    return '\t';
                case "{t}":
                    return '\t';
                case "tabulator":
                    return '\t';
                case "tab":
                    return '\t';
                default:
                    return Char.Parse(text);
            }
        }

        private string GetCultureName()
        {
            if (Properties.Settings.Default.CsvCultureName != "")
                return Properties.Settings.Default.CsvCultureName;
            else
                return Application.CurrentCulture.Name;
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            if (Properties.Settings.Default.FractalsDirectory == "")
                openFileDialog.InitialDirectory =
                    Environment.GetFolderPath(Environment.SpecialFolder.Personal) +
                    @"\Fractals";
            else
                openFileDialog.InitialDirectory = Properties.Settings.Default.FractalsDirectory;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                quantumFractal = new QFractal(openFileDialog.FileName);
                renderer = new QRenderer(Properties.Settings.Default.ResolutionPixels,
                    Properties.Settings.Default.ResolutionBalls);
                fractalPanel.Bitmap = new Bitmap(renderer.PixelResolution, renderer.PixelResolution);
                stName.Text = quantumFractal.Name;
                stResolution.Text = String.Format("{0}x{0}", renderer.PixelResolution);
                stMessage.Text = "Fractal loaded from file";
                ShowStatusTime();
                Properties.Settings.Default.FractalsDirectory = Path.GetDirectoryName(
                    openFileDialog.FileName);
                Properties.Settings.Default.Save();
            }
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            quantumFractal.Clear();
            renderer = new QRenderer(Properties.Settings.Default.ResolutionPixels,
                Properties.Settings.Default.ResolutionBalls);
            stResolution.Text = String.Format("{0}x{0}", renderer.PixelResolution);
            btnDraw_Click(sender, e);
            ShowStatusTime();
            stMessage.Text = "All calculated 3d points cleared";
        }

        public FractalObserver GetObserver(string text)
        {
            switch (text.ToLower())
            {
                case "front":
                    return FractalObserver.Front;
                case "back":
                    return FractalObserver.Back;
                case "left":
                    return FractalObserver.Left;
                case "right":
                    return FractalObserver.Right;
                default:
                    return FractalObserver.Front;
            }
        }

        private FractalPalette GetPalette()
        {
            switch(comPalette.Text.ToLower())
            {
                case "mono":
                    return FractalPalette.Mono;
                case "gray":
                    return FractalPalette.Gray;
                case "color":
                    return FractalPalette.Color;
                case "gray log":
                    return FractalPalette.GrayLog;
                case "color log":
                    return FractalPalette.ColorLog;
                case "gray historiogram":
                    return FractalPalette.GrayHistoriogram;
                case "color historiogram":
                    return FractalPalette.ColorHistoriogram;
                case "gray line":
                    return FractalPalette.GrayLine;
                case "color line":
                    return FractalPalette.ColorLine;
                default:
                    return FractalPalette.Mono;
            }
        }

        private void btnHistoriogram_Click(object sender, EventArgs e)
        {
            using (HistoriogramForm form = new HistoriogramForm())
            {
                form.ShowDialog(this);
            }
        }

        private void GetPrintPage(object sender, PrintPageEventArgs e)
        {
            float x = e.MarginBounds.Left;
            float y = e.MarginBounds.Top;

            Font font = new Font("Arial", 20);
            e.Graphics.DrawString("Fractal " + quantumFractal.Name + ", " + quantumFractal.Author, 
                font, Brushes.Black, x, y);
            y += font.GetHeight(e.Graphics);
            e.Graphics.DrawString("[" + comObserver.Text + ", " + comPalette.Text + ", " + 
                DateTime.Now.ToShortDateString() + "]", font, Brushes.Black, x, y);
            y += font.GetHeight(e.Graphics) * 2;

            e.Graphics.DrawImage(fractalPanel.Bitmap, x, y,
                e.MarginBounds.Right - e.MarginBounds.Left, e.MarginBounds.Right - e.MarginBounds.Left);

        }

        private void btnPrint_Click(object sender, EventArgs e)
        {
            PrintDocument doc = new PrintDocument();
            doc.DocumentName = "Fractal " + quantumFractal.Name;
            doc.PrintPage += GetPrintPage;

            PrintDialog dlgSettings = new PrintDialog();
            dlgSettings.Document = doc;

            if (dlgSettings.ShowDialog() == DialogResult.OK)
            {
                Application.DoEvents();
                doc.Print();
                stMessage.Text = "Fractal printed";
            }
        }

        private void btnCopyToClipboard_Click(object sender, EventArgs e)
        {
            Clipboard.SetImage(fractalPanel.Bitmap);
            stMessage.Text = "Fractal image is in the clipboard now";
        }

        private void btnDraw_Click(object sender, EventArgs e)
        {
            if (renderer.CalcSummary().ForObserver(GetObserver(comObserver.Text)) > 10)
            {
                Cursor = Cursors.WaitCursor;
                try
                {
                    Application.DoEvents();
                    fractalPanel.Bitmap = renderer.DrawFractal(GetObserver(comObserver.Text), GetPalette());
                }
                finally
                {
                    Cursor = Cursors.Default;
                }
            }
            else
            {
                stMessage.Text = "Please calculate more 3d points (minimum density 10)";
                return;
            }
        }

        private void btnDrawMoon_Click(object sender, EventArgs e)
        {
            if (renderer.CalcSummary().ForObserver(GetObserver(comObserver.Text)) > 10)
            {
                Cursor = Cursors.WaitCursor;
                try
                {
                    Application.DoEvents();
                    fractalPanel.Bitmap = renderer.DrawMoonFractal(GetObserver(comObserver.Text), GetPalette());
                }
                finally
                {
                    Cursor = Cursors.Default;
                }
            }
            else
            {
                stMessage.Text = "Please calculate more 3d points (minimum density 10)";
                return;
            }
        }

        private void fractalMainPanel_Resize(object sender, EventArgs e)
        {
            fractalPanel.Visible = false;
            fractalPanel.Height = fractalMainPanel.Height - 20;
            fractalPanel.Width = fractalPanel.Height;
            fractalPanel.Top = 10;
            fractalPanel.Left = fractalMainPanel.Width / 2 - fractalPanel.Width / 2;
            fractalPanel.Visible = true;
            fractalPanel.Refresh();
        }

        private void fractalPanel_Zoom(object sender, int zoom)
        {
            stZoom.Text = String.Format("{0}%", zoom);
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (running)
            {
                MessageBox.Show("Please wait to the finish of calculations or click Stop", "Warning", 
                    MessageBoxButtons.OK);
                e.Cancel = true;
            }
        }

        private void btn3dBalls_Click(object sender, EventArgs e)
        {
            using (BallsForm form = new BallsForm(renderer))
            {
                form.Show();
                form.StartRendering();
            }
        }
    }
}